package com.itu.modernai;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

/**
 *
 * @author Anders
 */
public class TrainedANN { // This is a simplified, streamlined ANN class that cannot train itself, but can build a trained neural network from a ANN scheme file. It can also classify a "best move" for the MyPacMan class
    private int numberOfInputs = 0;
    int neuronsPerLayer[];
    int currentPattern = -1;
    ArrayList<TrainedANN.Neuron> neurons;
    float output[]; 
    private float maxDist = 1500, maxVel = 600, maxDmg = 100;
    
    public class Neuron{
        public int ID;
        public float weightsIn[], dEa, dEs, dEweights[];
        public float output;
        public TrainedANN.Neuron ins[];
        public TrainedANN.Neuron outs[];
        public int layer;
        Neuron(int _ID, int _layer){
            output = 0;
            ID = _ID;
            layer = _layer;
        }
    }
    
    public TrainedANN(){
        neurons = new ArrayList<TrainedANN.Neuron>();
        numberOfInputs = 2;
    }  
    
  
    public void addNeuron(int ID, int layer){
        neurons.add(new TrainedANN.Neuron(ID, layer));
    }
    
    public static TrainedANN load(String fileName){
        TrainedANN neuralNet = new TrainedANN();
        try{
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
            String dataLine = br.readLine();
            int ID = 0;
            while(dataLine != null){                // Based on the data of the neurons from the text file, the pre-trained neural network is build.
                String[] types = dataLine.split(";");
                if(ID == 4){
                    neuralNet.maxDist = Float.parseFloat(types[0]);
                    neuralNet.maxVel = Float.parseFloat(types[1]);
                    neuralNet.maxDmg = Float.parseFloat(types[2]);
                }
                else{
                    neuralNet.addNeuron(ID, Integer.parseInt(types[0]));
                    neuralNet.neurons.get(ID).output = Float.parseFloat(types[1]);
                    String[] weightsInStr = types[2].split(",");
                    neuralNet.neurons.get(ID).weightsIn = new float[weightsInStr.length];
                    for(int i = 0; i < weightsInStr.length; i++)
                        neuralNet.neurons.get(ID).weightsIn[i] = Float.parseFloat(weightsInStr[i]);
                    if(types.length > 3){
                        String[] insStr = types[3].split(",");
                        neuralNet.neurons.get(ID).ins = new TrainedANN.Neuron[insStr.length];
                        for(int i = 0; i < insStr.length; i++)
                            neuralNet.neurons.get(ID).ins[i] = neuralNet.neurons.get(Integer.parseInt(insStr[i]));
                    }
                }
                dataLine = br.readLine();
                ID++;
            }
        }
        catch(IOException ioe){ioe.printStackTrace();}
        return neuralNet;
    }
    
    public int classifyDamage(double distance, double velocity){
        int damage = 0;
        float inputs[] = new float[numberOfInputs];        
        inputs[0] = (float)(distance / maxDist);
        inputs[1] = (float)(velocity / maxVel);
        
        float[] oldOutputs = new float[neurons.size()]; // Before using the inputs and forward mode to find the highest output neuron, the current outputs of the neurons is saved, since the outputs should only be changed temporarily
        double sigValue;
        for(int i = 0; i < neurons.size(); i++){
            sigValue = neurons.get(i).weightsIn[0];
            if(neurons.get(i).layer == 1){
                for(int j = 0; j < numberOfInputs; j++)
                    sigValue += inputs[j] * neurons.get(i).weightsIn[j+1];
            }
            else{
                for(int j = 0; j < neurons.get(i).ins.length; j++)
                    sigValue += neurons.get(i).ins[j].output * neurons.get(i).weightsIn[j + 1];
            }
            oldOutputs[i] = neurons.get(i).output;
            neurons.get(i).output = (float)(1 / (1 + Math.pow(Math.E, -sigValue)));
        }
        
        damage = (int)(neurons.get(neurons.size()-1).output*maxDmg);
        
        for(int i = 0; i < neurons.size(); i++) // The original output values of the neurons are restored since the classifier function is a "read-only" of the trained network
            neurons.get(i).output = oldOutputs[i];
        
        return damage;
    }
}

